package com.zackeus.sipservice;

import androidx.annotation.Nullable;

import com.elvishew.xlog.XLog;
import com.zackeus.common.utils.ObjectUtils;
import com.zackeus.common.utils.StringUtils;
import com.zackeus.common.utils.media.MediaManager;
import com.zackeus.sipservice.entity.Jitter;
import com.zackeus.sipservice.entity.RtpStreamStats;

import org.pjsip.pjsua2.AudDevManager;
import org.pjsip.pjsua2.AudioMedia;
import org.pjsip.pjsua2.Call;
import org.pjsip.pjsua2.CallInfo;
import org.pjsip.pjsua2.CallMediaInfo;
import org.pjsip.pjsua2.CallOpParam;
import org.pjsip.pjsua2.CallSetting;
import org.pjsip.pjsua2.Media;
import org.pjsip.pjsua2.OnCallMediaStateParam;
import org.pjsip.pjsua2.OnCallStateParam;
import org.pjsip.pjsua2.OnStreamDestroyedParam;
import org.pjsip.pjsua2.RtcpStreamStat;
import org.pjsip.pjsua2.StreamInfo;
import org.pjsip.pjsua2.StreamStat;
import org.pjsip.pjsua2.pjmedia_type;
import org.pjsip.pjsua2.pjsip_inv_state;
import org.pjsip.pjsua2.pjsip_role_e;
import org.pjsip.pjsua2.pjsip_status_code;
import org.pjsip.pjsua2.pjsua_call_flag;
import org.pjsip.pjsua2.pjsua_call_media_status;

/**
 * @name: SipCall
 * @author: zz
 * @date: 2022/9/20
 */
public class SipCall extends Call {

    private static final String LOG_TAG = SipCall.class.getSimpleName();

    private final SipAccount account;

    /* 视频通话 */
    private final boolean videoCall = Boolean.FALSE;
    private long confirmedTimestamp = 0;

    private StreamInfo streamInfo = null;
    private StreamStat streamStat = null;

    /* 静音 */
    private boolean localMute = false;
    /* 由系统处理 */
    private boolean disused = false;

    /**
     * 呼入构造函数
     * @param account the account which own this call
     * @param callId the id of this call
     */
    public SipCall(SipAccount account, int callId) {
        super(account, callId);
        this.account = account;
    }

    /**
     * 外呼构造函数
     * @param account account which owns this call
     */
    public SipCall(SipAccount account) {
        super(account);
        this.account = account;
    }

    /**
     * 获取当前通话状态
     * @return
     */
    public int getState() {
        try {
            CallInfo info = getInfo();
            return info.getState();
        } catch (Exception exc) {
            XLog.tag(LOG_TAG).e("Error while getting call Info", exc);
            return pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED;
        }
    }

    /**
     * 获取呼叫方向
     * @return
     */
    public Integer getRole() {
        try {
            CallInfo info = getInfo();
            return info.getRole();
        } catch (Exception exc) {
            XLog.tag(LOG_TAG).e("Error while getting call Role", exc);
            return null;
        }
    }

    /**
     * 获取桥接时间戳
     * @return
     */
    public long getConfirmedTimestamp() {
        return confirmedTimestamp;
    }

    public boolean isMute() {
        return localMute;
    }

    public boolean isDisused() {
        return disused;
    }

    public void setDisused(boolean disused) {
        this.disused = disused;
    }

    /**
     * 媒体设置
     * @param param
     */
    private void setMediaParams(CallOpParam param) {
        CallSetting callSetting = param.getOpt();
        /* 设置媒体流数量 0将禁用指定的媒体 */
        callSetting.setAudioCount(1);
        callSetting.setVideoCount(videoCall ? 1 : 0);
    }

    /**
     * 发送呼叫统计
     * @param callId
     * @param connectDuration
     * @param totalDuration
     * @param hangupStatus
     */
    private void sendCallStats(int callId, int connectDuration, int totalDuration, int hangupStatus) {
        String audioCodec = streamInfo.getCodecName().toLowerCase() + "_" + streamInfo.getCodecClockRate();

        /* 解码器流统计 */
        RtcpStreamStat rxStat = streamStat.getRtcp().getRxStat();
        /* 编码器流统计 */
        RtcpStreamStat txStat = streamStat.getRtcp().getTxStat();

        Jitter rxJitter = new Jitter(
                rxStat.getJitterUsec().getMin(),
                rxStat.getJitterUsec().getMax(),
                rxStat.getJitterUsec().getMean(),
                rxStat.getJitterUsec().getLast()
        );

        Jitter txJitter = new Jitter(
                txStat.getJitterUsec().getMin(),
                txStat.getJitterUsec().getMax(),
                txStat.getJitterUsec().getMean(),
                txStat.getJitterUsec().getLast()
        );

        RtpStreamStats rx = new RtpStreamStats(
                rxStat.getPkt(),
                rxStat.getBytes(),
                rxStat.getDiscard(),
                rxStat.getLoss(),
                rxStat.getReorder(),
                rxStat.getDup(),
                rxJitter
        );

        RtpStreamStats tx = new RtpStreamStats(
                txStat.getPkt(),
                rxStat.getBytes(),
                txStat.getDiscard(),
                txStat.getLoss(),
                txStat.getReorder(),
                txStat.getDup(),
                txJitter
        );

        account.getService().getEventEmitter().callStats(account.getData().getIdUri(), callId,
                connectDuration, totalDuration, audioCodec, hangupStatus, rx, tx);
        streamInfo = null;
        streamStat = null;
    }

    /**
     * 发送对INVITE请求的响应(仅适用于EARLY状态下的来电)
     * 根据指定的状态码，此功能可能发送临时响应，建立呼叫或终止呼叫
     * @param statusCode 状态码 (100-699)
     * @param reason 原因短语
     */
    public void answer(int statusCode, @Nullable String reason) throws Exception {
        CallOpParam param = new CallOpParam();
        param.setStatusCode(statusCode);
        if (StringUtils.isNotBlank(reason))
            param.setReason(reason);

        if (pjsip_status_code.PJSIP_SC_OK == statusCode) {
            /* 200 应答 */
            setMediaParams(param);
            if (!videoCall) {
                CallSetting callSetting = param.getOpt();
                callSetting.setFlag(pjsua_call_flag.PJSUA_CALL_INCLUDE_DISABLED_MEDIA);
            }
        }

        answer(param);
    }

    /**
     * 挂断
     * @param statusCode 拒绝状态码
     * @param reason 原因短语
     * @throws Exception
     */
    public void hangUp(int statusCode, @Nullable String reason) throws Exception {
        CallOpParam param = new CallOpParam();
        param.setStatusCode(statusCode);
        if (StringUtils.isNotBlank(reason))
            param.setReason(reason);
        hangup(param);
    }

    @Override
    public void makeCall(String dst_uri, CallOpParam prm) throws Exception {
        setMediaParams(prm);
        if (!videoCall) {
            CallSetting callSetting = prm.getOpt();
            callSetting.setFlag(pjsua_call_flag.PJSUA_CALL_INCLUDE_DISABLED_MEDIA);
        }
        super.makeCall(dst_uri, prm);
    }

    /**
     * 静音
     * @param mute
     */
    public void setMute(boolean mute) throws Exception {
        if (localMute == mute) return;

        CallInfo info;
        try {
            info = getInfo();
        } catch (Exception exc) {
            XLog.tag(LOG_TAG).e("setMute: error while getting call info", exc);
            throw exc;
        }

        for (int i = 0; i < info.getMedia().size(); i++) {
            Media media = getMedia(i);
            CallMediaInfo mediaInfo = info.getMedia().get(i);

            if (mediaInfo.getType() == pjmedia_type.PJMEDIA_TYPE_AUDIO
                    && media != null
                    && mediaInfo.getStatus() == pjsua_call_media_status.PJSUA_CALL_MEDIA_ACTIVE) {
                AudioMedia audioMedia = AudioMedia.typecastFromMedia(media);

                try {
                    AudDevManager mgr = account.getService().getAudDevManager();
                    if (mute) mgr.getCaptureDevMedia().stopTransmit(audioMedia);
                    else mgr.getCaptureDevMedia().startTransmit(audioMedia);
                    localMute = mute;
                    /// TODO: 2022/11/3 发送音频状态事件
                } catch (Exception exc) {
                    XLog.tag(LOG_TAG).e("setMute: error while connecting audio media to sound device", exc);
                    throw exc;
                }
            }
        }
    }

    @Override
    public void onCallMediaState(OnCallMediaStateParam prm) {
        CallInfo info;
        try {
            info = getInfo();
        } catch (Exception exc) {
            XLog.tag(LOG_TAG).e("onCallMediaState: error while getting call info", exc);
            return;
        }

        for (int i = 0; i < info.getMedia().size(); i++) {
            Media media = getMedia(i);
            CallMediaInfo mediaInfo = info.getMedia().get(i);

            if (ObjectUtils.isNotNull(media) && mediaInfo.getType() == pjmedia_type.PJMEDIA_TYPE_AUDIO
                    && mediaInfo.getStatus() == pjsua_call_media_status.PJSUA_CALL_MEDIA_ACTIVE) {

                AudioMedia audioMedia = AudioMedia.typecastFromMedia(media);
                try {
                    if (ObjectUtils.isNull(audioMedia))
                        continue;

                    try {
                        /* 调整音量 */
                        audioMedia.adjustRxLevel((float) 1.5);
                        audioMedia.adjustTxLevel((float) 1.5);
                    } catch (Exception exc) {
                        XLog.tag(LOG_TAG).e("Error while adjusting levels", exc);
                    }

                    /* 将处于活动状态的音频媒体连接到声音设备 */
                    AudDevManager audDevManager = account.getService().getAudDevManager();
                    audioMedia.startTransmit(audDevManager.getPlaybackDevMedia());
                    audDevManager.getCaptureDevMedia().startTransmit(audioMedia);
                } catch (Exception exc) {
                    XLog.tag(LOG_TAG).e("Error while connecting audio media to sound device", exc);
                }

            }
        }
    }

    @Override
    public void onStreamDestroyed(OnStreamDestroyedParam prm) {
        long idx = prm.getStreamIdx();
        try {
            if (getInfo().getMedia().get((int)idx).getType() == pjmedia_type.PJMEDIA_TYPE_AUDIO) {
                streamInfo = getStreamInfo(idx);
                streamStat = getStreamStat(idx);
            }
        } catch (Exception ex) {
            XLog.tag(LOG_TAG).e("onStreamDestroyed: error while getting call stats", ex);
        }
        super.onStreamDestroyed(prm);
    }

    @Override
    public void onCallState(OnCallStateParam prm) {
        try {
            CallInfo info = getInfo();
            int callId = info.getId();
            int stateCode = info.getState();
            int statusCode = pjsip_status_code.PJSIP_SC_NULL;
            /* 呼叫方向 呼出(UAC) 呼入(UAS) */
            int callRole = info.getRole();

            try {
                statusCode = info.getLastStatusCode();
            } catch(Exception ex) {
                XLog.tag(LOG_TAG).e("Error while getting call status", ex);
            }

            switch (stateCode) {
                case pjsip_inv_state.PJSIP_INV_STATE_CALLING:
                    if (callRole == pjsip_role_e.PJSIP_ROLE_UAC) {
                        /* 发送外呼发起事件 */
                        SipUri.SipContactInfo contactInfo = SipUri.parseSipContact(info.getRemoteUri());
                        account.getService().getEventEmitter().outgoingCall(account.getData().getIdUri(),
                                callId, contactInfo.userName, contactInfo.displayName);
                    }
                    break;

                case pjsip_inv_state.PJSIP_INV_STATE_EARLY:
                    /* 振铃 */
                    if (statusCode == pjsip_status_code.PJSIP_SC_RINGING && callRole == pjsip_role_e.PJSIP_ROLE_UAC) {
                        /* 180 并且是外呼(UAC) */
                        MediaManager.getInstance().startRingBackTone();
                    } else if (statusCode == pjsip_status_code.PJSIP_SC_PROGRESS) {
                        /* 183 */
                        MediaManager.getInstance().stopAll();
                    }
                    break;

                case pjsip_inv_state.PJSIP_INV_STATE_CONFIRMED:
                    /* 对方已接听 通话建立 */
                    MediaManager.getInstance().stopAll();
                    confirmedTimestamp = System.currentTimeMillis();
                    /* 取消来电通知 */
                    this.account.getService().cancelNotify(callId);
                    break;

                case pjsip_inv_state.PJSIP_INV_STATE_DISCONNECTED:
                    /* 通话断开 */
                    if (!this.disused) {
                        /* 非系统处理的会话 停止播放提示音 */
                        MediaManager.getInstance().stopAll();
                    }
                    /* 取消来电通知 */
                    this.account.getService().cancelNotify(callId);

                    if (confirmedTimestamp > 0 && streamInfo != null && streamStat != null) {
                        /* 发送呼叫统计信息 */
                        sendCallStats(callId, info.getConnectDuration().getSec(), info.getTotalDuration().getSec(), statusCode);
                    }

                    if (callRole == pjsip_role_e.PJSIP_ROLE_UAS && confirmedTimestamp == 0 && this.disused) {
                        /// TODO: 发送未接来电事件(主动处理)
                        // account.getService().getEventEmitter().missedCall(contactInfo.getDisplayName(), contactInfo.getRemoteUri());
                    }

                    /* 断开后调用将引发异常 应删除回调对象 */
                    /* see: http://www.pjsip.org/docs/book-latest/html/call.html#call-disconnection */
                    account.removeCall(callId);
                    delete();
                    break;

                default:
                    break;
            }

            account.getService().getEventEmitter().callState(account.getData().getIdUri(),
                    callId, callRole, stateCode, statusCode, confirmedTimestamp);
        } catch (Exception exc) {
            XLog.tag(LOG_TAG).e("onCallState: error while getting call info", exc);
        }
    }
}
